package com.googlecode.cswish.struts.spring;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.struts.hibernate.HibernateConfiguration;

public class SiteAnnotationAsyncExecutionInterceptor extends AnnotationAsyncExecutionInterceptor {

	private static final Log logger = LogFactory.getLog(SiteAnnotationAsyncExecutionInterceptor.class);
	
	private HibernateConfiguration configuration;
	
	private HibernateConfiguration getHibernateConfiguration() {
		if (configuration == null) {
			configuration = ObjectFactory.getInstance().getBean(HibernateConfiguration.class, false);
		}
		return configuration;
	}
	
	public SiteAnnotationAsyncExecutionInterceptor(Executor defaultExecutor) {
		super(defaultExecutor);
	}
	
	
	@Override
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

		AsyncTaskExecutor executor = determineAsyncExecutor(specificMethod);
		if (executor == null) {
			throw new IllegalStateException(
					"No executor specified and no default executor set on AsyncExecutionInterceptor either");
		}

		final String siteName = ContextHolder.get().getSiteName();
		Future<?> result = executor.submit(
				new Callable<Object>() {
					
					public Object call() throws Exception {
						try {
							ContextHolder.get().setSiteName(siteName);
							
							EntityManagerFactory emf = getHibernateConfiguration().getEntityManagerFactory();
							EntityManager em = emf.createEntityManager();
							TransactionSynchronizationManager.bindResource(emf,
									new EntityManagerHolder(em));
							
							Object result = invocation.proceed();
							
							if (result instanceof Future) {
								return ((Future<?>) result).get();
							}
						}
						catch (Throwable ex) {
							logger.error("Async Exception", ex);
							ReflectionUtils.rethrowException(ex);
						} finally {
							EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
									.unbindResource(getHibernateConfiguration().getEntityManagerFactory());
							EntityManagerFactoryUtils.closeEntityManager(emHolder
									.getEntityManager());
						}
						return null;
					}
				});

		if (Future.class.isAssignableFrom(invocation.getMethod().getReturnType())) {
			return result;
		}
		else {
			return null;
		}
	}
}